/*-------------------------------------------------------------------------
 *
 * pg_strong_random.c
 *	  生成一个密码安全的随机数
 *
 * 我们对 “强” 的定义是，它适合在身份验证过程中生成随机盐和查询取消密钥。
 *
 * 注意：这段代码在 postmaster 和后端启动的很早阶段运行；因此，即使为后端构建，也不能依赖于后端基础设施，如 elog() 或 palloc()。
 *
 * Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/port/pg_strong_random.c
 *
 *-------------------------------------------------------------------------
 */

#include "c.h"

#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>

/*
 * pg_strong_random & pg_strong_random_init
 *
 * 生成请求数量的随机字节。返回的字节是
 * 具有密码学安全性的，适合于例如在身份验证中使用。
 *
 * 在任何进程中调用 pg_strong_random 之前，生成器必须首先
 * 通过调用 pg_strong_random_init() 进行初始化。
 *
 * 我们依赖于系统设施来实际生成数字。
 * 我们支持多种来源：
 *
 * 1. OpenSSL 的 RAND_bytes()
 * 2. Windows 的 CryptGenRandom() 函数
 * 3. /dev/urandom
 *
 * 成功时返回真，若没有可用的来源则返回假。注意：检查返回值非常重要！
 * 在没有可用随机数据的情况下进行密钥生成将导致可预测的密钥和安全问题。
 */



#ifdef USE_OPENSSL

#include <openssl/rand.h>

void pg_strong_random_init(void)
{
	/*
	 * 确保进程不共享 OpenSSL 随机状态。这在 OpenSSL 1.1.1 及更高版本中不再需要，但在我们停止支持版本 < 1.1.1 之前，我们需要这样做。
	 */
	RAND_poll();
}

bool pg_strong_random(void *fc_buf, size_t fc_len)
{
	int			fc_i;

	/*
	 * 检查 OpenSSL 的 CSPRNG 是否已经充分播种，如果没有，
	 * 使用 RAND_poll() 添加更多种子数据。在一些旧版本的
	 * OpenSSL 中，可能需要调用 RAND_poll() 多次。如果
	 * RAND_poll() 在给定的重试次数内无法生成种子数据，
	 * 随后的 RAND_bytes() 调用将会失败，但我们允许这样的情况
	 * 发生，以便 pg_strong_random() 的调用者能够适当地处理
	 * 异常。
	 */
#define NUM_RAND_POLL_RETRIES 8

	for (fc_i = 0; fc_i < NUM_RAND_POLL_RETRIES; fc_i++)
	{
		if (RAND_status() == 1)
		{
			/* CSPRNG 已充分播种 */
			break;
		}

		RAND_poll();
	}

	if (RAND_bytes(fc_buf, fc_len) == 1)
		return true;
	return false;
}

#elif WIN32

#include <wincrypt.h>
/*
 * 缓存一个全局加密提供者，该提供者仅在进程
 * 退出时释放，以防我们需要多次获得随机数字。
 */
static HCRYPTPROV hProvider = 0;

void pg_strong_random_init(void)
{
	/* 在 WIN32 上不需要初始化 */
}

bool pg_strong_random(void *buf, size_t len)
{
	if (hProvider == 0)
	{
		if (!CryptAcquireContext(&hProvider,
								 NULL,
								 MS_DEF_PROV,
								 PROV_RSA_FULL,
								 CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
		{
			/*
			 * 失败时，回退到 0，以防值因某种原因
			 * 被修改。
			 */
			hProvider = 0;
		}
	}
	/* 重新检查，以防我们刚刚获取了提供者 */
	if (hProvider != 0)
	{
		if (CryptGenRandom(hProvider, len, buf))
			return true;
	}
	return false;
}

#else							/* not USE_OPENSSL or WIN32 */

/*
 * 在没有 OpenSSL 或 Win32 支持的情况下，自己读取 /dev/urandom。
 */

void pg_strong_random_init(void)
{
	/* 不需要初始化 */
}

bool pg_strong_random(void *fc_buf, size_t fc_len)
{
	int			fc_f;
	char	   *fc_p = fc_buf;
	ssize_t		fc_res;

	fc_f = open("/dev/urandom", O_RDONLY, 0);
	if (fc_f == -1)
		return false;

	while (fc_len)
	{
		fc_res = read(fc_f, fc_p, fc_len);
		if (fc_res <= 0)
		{
			if (errno == EINTR)
				continue;		/* 被信号中断，重新尝试 */

			close(fc_f);
			return false;
		}

		fc_p += fc_res;
		fc_len -= fc_res;
	}

	close(fc_f);
	return true;
}
#endif
